﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Actor.Net.Network.Gate.WebSocket
{
    public class GateWebSocketService : GateBaseService
    {
        private HttpListener Listener { get; set; }
        public string HttpPrefixed { get; set; }

        public GateWebSocketService(GateNetwork network) : base(network) 
        {
            this.HttpPrefixed = this.Network.HttpPrefixed;
        }

        public override GateChannelContext Connect(int timeoutMillisecond)
        {
            //this.HttpPrefixed = $"ws://{this.Network.ConnectEndPoint.Address}:{this.Network.ConnectEndPoint.Port}/";
            //this.HttpPrefixed = this.Network.HttpPrefixed;
            this.ConnectChannel = ConnectChannel ?? new GateWebSocketChannel(this, this.CreateChannelId());
            this.ConnectChannel.Connect(timeoutMillisecond);
            return this.ConnectChannel.ChannelContext;
        }

        public override async Task<GateChannelContext> ConnectAsync(CancellationToken cancellationToken)
        {
            //this.HttpPrefixed = $"ws://{this.Network.ConnectEndPoint.Address}:{this.Network.ConnectEndPoint.Port}/";
            //this.HttpPrefixed = this.Network.HttpPrefixed;
            this.ConnectChannel = ConnectChannel ?? new GateWebSocketChannel(this, this.CreateChannelId());
            await this.ConnectChannel.ConnectAsync(cancellationToken);
            return this.ConnectChannel.ChannelContext;
        }

        public override async void Listen()
        {
            if (this.Listener != null)
                return;

            this.Listener = new HttpListener();
            //this.HttpPrefixed = $"http://{this.Network.ConnectEndPoint.Address}:{this.Network.ConnectEndPoint.Port}/";
            var prefixed = string.Empty;

            if (this.HttpPrefixed.StartsWith("wss://"))
                prefixed = this.HttpPrefixed.Replace("wss://", "https://");
            else if(this.HttpPrefixed.StartsWith("ws://"))
                prefixed = this.HttpPrefixed.Replace("ws://", "http://");

            this.Listener.Prefixes.Add(prefixed);
            this.Listener.Start();
            while (true)
            {
                try
                {
                    var context = await this.Listener.GetContextAsync();
                    var channel = await HandleAccept(context);
                    //await GameNetwork.SynchronizationThreadContext;
                    this.ProcessConnected(channel.ChannelContext);
                }
                catch(Exception ex)
                {
                    this.ProcessSocketError(null, ex);
                }
            }
        }

        private async Task<GateWebSocketChannel> HandleAccept(HttpListenerContext context)
        {
            var wsContext = await context.AcceptWebSocketAsync(null);
            var client = wsContext.WebSocket;
            var channel = new GateWebSocketChannel(client, this, this.CreateChannelId(), context.Request.LocalEndPoint, context.Request.RemoteEndPoint);
            return channel;
        }

        public override void Update()
        {
            if (this.ServiceType == NetServiceType.Undefined)
                return;

            if (this.ServiceType == NetServiceType.Client)
            {
                if (this.ConnectChannel == null)
                    return;

                this.ConnectChannel.StartSend();
            }
            else
            {
                foreach (var context in ConnectingChannels.Values)
                {
                    if (context.Channel.IsDropped && context.Channel.Network.IsIsDropTimeout)
                    {
                        context.Channel.Disconnect();
                        continue;
                    }
                    context.Channel.StartSend();
                }
            }
        }

        internal override void ProcessConnected(GateChannelContext context)
        {
            if (GateBaseService.ChannelContexts.ContainsKey(context.ChannelId))
                return;

            if (!context.Channel.Connected)
                return;

            GateBaseService.ChannelContexts.AddOrUpdate(context.ChannelId, context, (k, v) => context);
            this.ConnectingChannels.AddOrUpdate(context.ChannelId, context, (k, v) => context);
            try
            {
                this.OnConnected?.Invoke(context);
            }
            catch (Exception ex)
            {
                this.ProcessSocketError(context, ex);
            }
        }

        internal override void ProcessDisconnected(GateChannelContext context)
        {
            if (!GateBaseService.ChannelContexts.TryRemove(context.ChannelId, out context))
                return;

            this.ConnectingChannels.TryRemove(context.ChannelId, out _);

            if (this.ServiceType == NetServiceType.Client)
            {
                try
                {
                    this.OnDisconnected?.Invoke(context);
                }
                catch (Exception ex)
                {
                    this.ProcessSocketError(context, ex);
                }
                return;
            }

            try
            {
                this.OnDisconnected?.Invoke(context);
            }
            catch (Exception ex)
            {
                this.ProcessSocketError(context, ex);
            }
        }

        public override void Dispose()
        {
            if (this.ServiceType == NetServiceType.Client)
            {
                this.ConnectChannel.Disconnect();
            }
            else
            {
                foreach (var context in this.ConnectingChannels.Values)
                {
                    context.Channel.Disconnect();
                }
            }
        }
    }
}
